/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package common

import (
	"fmt"

	"hundsun.com/hsl/hschain/protos/ledger"

	lru "github.com/hashicorp/golang-lru"
	"hundsun.com/hsl/hschain/common/types"
	pbcom "hundsun.com/hsl/hschain/protos/common"
	dbm "hundsun.com/hsl/hschain/store/db"
	"hundsun.com/hsl/hschain/store/mq"
	"hundsun.com/hsl/hschain/store/state"
)

// NewExecContent ...
func NewExecContent(header *ledger.BlockHeader, stateHash []byte, ledgerHdl dbm.Database, stateHdl *StateHandle) *ExecContent {
	return &ExecContent{
		header:    header,
		stateHash: stateHash,
		stateHdl:  stateHdl,
		ledgerHdl: ledgerHdl,
	}
}

// GetBlockHeader ...
func (ec *ExecContent) GetBlockHeader() *ledger.BlockHeader {
	return ec.header
}

// GetHeight ...
func (ec *ExecContent) GetHeight() uint64 {
	return ec.header.GetNumber()
}

// GetBlockTime ...
func (ec *ExecContent) GetBlockTime() int64 {
	return ec.header.GetBlockTime()
}

// GetStateHash 获取当前状态hash
func (ec *ExecContent) GetStateHash() []byte {
	return ec.stateHash
}

// GetStateHandle 获取状态处理
func (ec *ExecContent) GetStateHandle() *StateHandle {
	return ec.stateHdl
}

// GetLedger 获取账本数据
func (ec *ExecContent) GetLedger() dbm.Database {
	return ec.ledgerHdl
}

// Get 获取当前stateHash下的数据
func (ec *ExecContent) Get(key []byte) ([]byte, error) {
	return ec.stateHdl.Get(ec.stateHash, key)
}

// Gets 批量获取当前stateHash下的数据
func (ec *ExecContent) Gets(keys [][]byte) ([][]byte, error) {
	return ec.stateHdl.Gets(ec.stateHash, keys)
}

// NewStateHandle ...
func NewStateHandle(client mq.Client, preState map[string][]byte, statedb state.State) *StateHandle {
	cache, err := lru.New(1024 * 10)
	if err != nil {
		panic(fmt.Sprintln("new state handle fail", err))
	}
	return &StateHandle{
		client:    client,
		tempCache: make(map[string][]byte),
		preState:  preState,
		statedb:   statedb,
		lruState:  cache,
	}
}

// Get 根据stateHash、height获取相应的value
func (sdb *StateHandle) Get(stateHash []byte, key []byte) ([]byte, error) {
	// 先从本地缓存中获取
	if v, ok := sdb.getLocal(string(key)); ok {
		return v, nil
	}
	// 最后从db中获取
	if sdb.statedb != nil {
		datas := &pbcom.StateGet{
			StateHash: stateHash,
			Keys:      [][]byte{key},
		}
		vs, _ := sdb.statedb.Get(datas)
		if len(vs) > 0 && len(vs[0]) > 0 {
			// 先加入db缓存中
			if sdb.lruState != nil {
				sdb.lruState.Add(string(key), vs[0])
			}
			return vs[0], nil
		}
	}
	return nil, types.ErrNotFound
}

// Gets 根据stateHash、height获取相应的values
func (sdb *StateHandle) Gets(stateHash []byte, keys [][]byte) ([][]byte, error) {
	// 先从本地缓存中获取
	values := make([][]byte, len(keys))
	var dbScanIndex []int
	var dbScanKey [][]byte
	for i, k := range keys {
		v, ok := sdb.getLocal(string(k))
		if ok {
			values[i] = v
		} else {
			dbScanIndex = append(dbScanIndex, i)
			dbScanKey = append(dbScanKey, k)
		}
	}

	if len(dbScanKey) == 0 {
		return values, nil
	}

	// 最后从db中获取
	if sdb.statedb != nil {
		datas := &pbcom.StateGet{
			StateHash: stateHash,
			Keys:      dbScanKey,
		}
		vs, err := sdb.statedb.Get(datas)
		// 合并
		for i, v := range vs {
			if len(v) > 0 {
				if sdb.lruState != nil {
					sdb.lruState.Add(string(dbScanKey[i]), v)
				}
				values[dbScanIndex[i]] = v
			}
		}
		return values, err
	}
	return values, types.ErrNotFound
}

func (sdb *StateHandle) getLocal(key string) ([]byte, bool) {
	// 1. 从临时缓存中获取
	if v, ok := sdb.tempCache[key]; ok {
		return v, true
	}
	// 2. 再从preState中获取
	if sdb.preState != nil {
		if v, ok := sdb.preState[key]; ok {
			return v, true
		}
	}
	// 3. 从lruState中获取
	if sdb.lruState != nil {
		if v, ok := sdb.lruState.Get(key); ok {
			return v.([]byte), true
		}
	}
	return nil, false
}

// SetLocal value为nil时候对其清除
func (sdb *StateHandle) SetLocal(key, value []byte) {
	// set时候设置到临时缓存
	if value == nil {
		delete(sdb.tempCache, string(key))
		return
	}
	sdb.tempCache[string(key)] = value
}

// UpdatePreState 从本地更新到全局缓存中
func (sdb *StateHandle) UpdatePreState() {
	for k, v := range sdb.tempCache {
		sdb.preState[k] = v
	}
	// 清除localcache
	sdb.ClearLocal()
}

// ClearLocal 清除local缓存
func (sdb *StateHandle) ClearLocal() {
	// 清除localcache
	sdb.tempCache = make(map[string][]byte)
}
